from pyparsing import (Word, alphanums, nums, Keyword, Group, Combine,
                       Forward, Suppress, Optional, OneOrMore, oneOf,
                       QuotedString, quotedString, ZeroOrMore,
                       delimitedList, restOfLine, removeQuotes)

# Copyright (c) 2007 Michel Pelletier
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

def parser():
    """

    Parser tests.  This function returns a pyparsing parser object
    that define the language.  The result of using the parser object
    is a ParseResults object that is an object structured to represent
    the parsed result.  The asXML() method is a good way to test and
    visualize this structure.
    
    >>> p = parser()
    >>> def t(q): print p.parseString(q).asXML()

    >>> t('x = 3')
    <BLANKLINE>
    <query>
      <eq>
        <lval>x</lval>
        <int>3</int>
      </eq>
    </query>

    >>> t('from a_source where x = 3')
    <BLANKLINE>
    <query>
      <source>a_source</source>
      <eq>
        <lval>x</lval>
        <int>3</int>
      </eq>
    </query>

    >>> t('from default where z = 3')
    <BLANKLINE>
    <query>
      <source>default</source>
      <eq>
        <lval>z</lval>
        <int>3</int>
      </eq>
    </query>

    >>> t('x = 3 and y = "four"')
    <BLANKLINE>
    <query>
      <and>
        <eq>
          <lval>x</lval>
          <int>3</int>
        </eq>
        <eq>
          <lval>y</lval>
          <string>four</string>
        </eq>
      </and>
    </query>

    >>> t('x != "four" or z >= 7.3')
    <BLANKLINE>
    <query>
      <or>
        <ne>
          <lval>x</lval>
          <string>four</string>
        </ne>
        <ge>
          <lval>z</lval>
          <float>7.3</float>
        </ge>
      </or>
    </query>

    >>> t("w <= 'zing' and (_foo != 3 or x >= 3.145)")
    <BLANKLINE>
    <query>
      <and>
        <le>
          <lval>w</lval>
          <string>zing</string>
        </le>
        <parens>
          <or>
            <ne>
              <lval>_foo</lval>
              <int>3</int>
            </ne>
            <ge>
              <lval>x</lval>
              <float>3.145</float>
            </ge>
          </or>
        </parens>
      </and>
    </query>

    >>> t('x in ( 1, 2, 3 )')
    <BLANKLINE>
    <query>
      <in>
        <lval>x</lval>
        <vals>
          <int>1</int>
          <int>2</int>
          <int>3</int>
        </vals>
      </in>
    </query>

    >>> t('x in ( 1, 2.2, "three" )')
    <BLANKLINE>
    <query>
      <in>
        <lval>x</lval>
        <vals>
          <int>1</int>
          <float>2.2</float>
          <string>three</string>
        </vals>
      </in>
    </query>

    >>> t('x = ( 1 .. 5 )')
    <BLANKLINE>
    <query>
      <range>
        <lval>x</lval>
        <min>1</min>
        <max>5</max>
      </range>
    </query>

    >>> t('x = ( .. 7 )')
    <BLANKLINE>
    <query>
      <range>
        <lval>x</lval>
        <min>None</min>
        <max>7</max>
      </range>
    </query>

    >>> t('x = ( 3 .. )')
    <BLANKLINE>
    <query>
      <range>
        <lval>x</lval>
        <min>3</min>
        <max>None</max>
      </range>
    </query>

    >>> t('from foo where x = "three" and (z in ( 1, 2, 4 ) and y = ( .. 9 )) or fiz = 3.3')
    <BLANKLINE>
    <query>
      <source>foo</source>
      <or>
        <and>
          <eq>
            <lval>x</lval>
            <string>three</string>
          </eq>
          <parens>
            <and>
              <in>
                <lval>z</lval>
                <vals>
                  <int>1</int>
                  <int>2</int>
                  <int>4</int>
                </vals>
              </in>
              <range>
                <lval>y</lval>
                <min>None</min>
                <max>9</max>
              </range>
            </and>
          </parens>
        </and>
        <eq>
          <lval>fiz</lval>
          <float>3.3</float>
        </eq>
      </or>
    </query>

    """

    eq = Keyword("=").suppress()
    ne = Keyword("!=").suppress()
    ge = Keyword(">=").suppress()
    le = Keyword("<=").suppress()
    lparen = Keyword("(").suppress()
    rparen = Keyword(")").suppress()
    dotdot = Keyword("..").suppress()
    sign   = Word("+-", exact=1)

    From_ = Keyword("from", caseless=True).suppress()
    Where_ = Keyword("where", caseless=True).suppress()
    Source_ = (Word(alphanums + "_$") | Keyword("default")).setResultsName('source')


    Int_  = Combine(Optional(sign) + Word(nums)).setParseAction(lambda l, s, t: int(t[0])).setResultsName('int')
    Float_ = Combine(Optional(sign) + (Word(nums) + "." + Optional(Word(nums))  |
                                               ("." + Word(nums))) + 
                        Optional("E" + Optional(sign) + Word(nums))).setParseAction(lambda l, s, t: float(t[0])).setResultsName('float')

    Term_ = Word(alphanums + "_$").setResultsName('term')
    Lval_ = Word(alphanums + "_$").setResultsName('lval')
    String_ = quotedString.copy().setParseAction(removeQuotes).setResultsName('string')

    Rval_ = Float_ | Int_ | Term_ | String_
    Vals_ = Group(delimitedList(Rval_)).setResultsName('vals')

    In_  = Group(Lval_ + Keyword("in", caseless=True).suppress() + lparen + Vals_ + rparen).setResultsName('in')

    Eq_ = Group(Lval_ + eq + Rval_).setResultsName('eq')
    Ne_ = Group(Lval_ + ne + Rval_).setResultsName('ne')
    Ge_ = Group(Lval_ + ge + Rval_).setResultsName('ge')
    Le_ = Group(Lval_ + le + Rval_).setResultsName('le')

    Range_ = Group(Lval_ + Keyword("=").suppress() +
                   lparen +
                       Optional(Rval_, default=None).setResultsName('min') + dotdot +
                       Optional(Rval_, default=None).setResultsName('max') +
                   rparen).setResultsName('range')

    Expression_ = Eq_ | Ne_ | Ge_ | Le_ | In_ | Range_

    Or_ = Forward()

    Parens_ = Group(Suppress("(") + Or_ + Suppress(")")).setResultsName("parens") | Expression_

    Not_ = Forward()
    Not_ << (Group(Suppress(Keyword("not", caseless=True)) + Not_
    ).setResultsName("not") | Parens_)

    And_ = Forward()
    And_ << (Group(Not_ + Suppress(Keyword("and", caseless=True)) + And_
                   ).setResultsName("and") | Not_)

    Or_ << (Group(And_ + Suppress(Keyword("or", caseless=True)) + Or_
                  ).setResultsName("or") | And_)

    Query_ = (Optional((From_ + Source_ + Where_)) + Or_).setResultsName('query')
    Query_.ignore("#" + restOfLine)
    return Query_


# these are example query objects for the 'aql' class, which is below.
# These are used for doctest purposes.  Typically to use aql you would
# subclass the aql class and generate your own objects, not these.

class Term(object):
    """
    Superclass of objects used to define abstract query structure.
    """

class FieldTerm(Term):
    """
    A term associated with a named field.
    """
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return self.name

class And(Term):
    def __init__(self, *terms):
        self.terms = terms

    def __repr__(self):
        return "And"+`tuple(self.terms)`

class Or(Term):
    def __init__(self, *terms):
        self.terms = terms

    def __repr__(self):
        return "Or"+`tuple(self.terms)`

class Not(Term):
    def __init__(self, term):
        self.term = term

    def __repr__(self):
        return "Not("+`self.term`+")"

class Text(FieldTerm):
    def __init__(self, name, text):
        super(Text, self).__init__(name)
        self.text = text

    def __repr__(self):
        return "Text("+self.name+", "+self.text+")"

class Eq(FieldTerm):
    def __init__(self, name, value):
        super(Eq, self).__init__(name)
        self.value = value

    def __repr__(self):
        return "Eq("+self.name+", "+`self.value`+")"

class Ne(FieldTerm):
    def __init__(self, name, not_value):
        super(Ne, self).__init__(name)
        self.not_value = not_value

    def __repr__(self):
        return "Ne("+self.name+", "+`self.not_value`+")"

class Range(FieldTerm):
    def __init__(self, name, min_value, max_value):
        super(Range, self).__init__(name)
        self.min_value = min_value
        self.max_value = max_value

    def __repr__(self):
        return "Range("+self.name+", "+`self.min_value`+", "+`self.max_value`+")"

class Ge(Range):
    def __init__(self, name, min_value):
        super(Ge, self).__init__(name, min_value, None)

    def __repr__(self):
        return "Ge("+self.name+", "+`self.min_value`+")"

class Le(Range):
    def __init__(self, name, max_value):
        super(Le, self).__init__(name, None, max_value)

    def __repr__(self):
        return "Le("+self.name+", "+`self.max_value`+")"

class In(FieldTerm):
    def __init__(self, name, values):
        super(In, self).__init__(name)
        self.values = values

    def __repr__(self):
        return "In("+self.name+", "+`list(self.values)`+")"

class Source(object):

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "Source("+self.name+")"

class Query(object):

    def __init__(self, source, query):
        self.source = source
        self.query = query

    def __repr__(self):
        s = ''
        if self.source:
            s = ", %s" % self.source
        return "Query("+`self.query`+s+")"

class aql(object):

    """
    This class implements the grammar defined in parser() above.  When
    this object is called with a query string, the parser is run over
    the string and the resulting pyparsing.ParseResults object is
    evaluated by this class's evaluate() method.  Typically these
    methods are defined on a class that then either directly queries a
    search source or generates a 'query object' for a given search
    interface.

    Every feature in the language can be hooked from this interface.
    For features that are in the language that your platform doesn't
    support, raise a SyntaxError.  

    >>> a = aql()

    >>> a('x = 1 and y != 2')
    Query(And(Eq(x, 1), Ne(y, 2)))

    >>> a('z = "foo" or (z != 3.0 and fiz != "fozz")')
    Query(Or(Eq(z, 'foo'), And(Ne(z, 3.0), Ne(fiz, 'fozz'))))

    >>> a('z = "foo" and not x = 3')
    Query(And(Eq(z, 'foo'), Not(Eq(x, 3))))

    >>> a('z >= 3 or z <= 5')
    Query(Or(Ge(z, 3), Le(z, 5)))
    
    >>> a('z <= 3 or z >= 4')
    Query(Or(Le(z, 3), Ge(z, 4)))

    >>> a('x = ( .. )')
    Query(Range(x, None, None))
    
    >>> a('x = ( 1 .. 3.0 )')
    Query(Range(x, 1, 3.0))
    
    >>> a('x = ( 1.0 .. )')
    Query(Range(x, 1.0, None))
    
    >>> a('x = ( .. "three" )')
    Query(Range(x, None, 'three'))

    >>> a('x in ( 1, 2, 3 ) or z >= 4')
    Query(Or(In(x, [1, 2, 3]), Ge(z, 4)))
    
    >>> a('z <= 3 and z = ( 1 .. 2 )')    
    Query(And(Le(z, 3), Range(z, 1, 2)))
    
    """

    def __init__(self):
        self._methods = {
            'and': self.evalAnd,
            'or': self.evalOr,
            'not': self.evalNot,
            'parens': self.evalParens,
            'string': self.evalString,
            'eq': self.evalEq,
            'ne': self.evalNe,
            'ge': self.evalGe,
            'le': self.evalLe,
            'range': self.evalRange,
            'in': self.evalIn,
            'source': self.evalSource,
            'query': self.evalQuery
        }
        self._parser = parser().parseString
    
    def evalAnd(self, arg):
        return And(self.eval(arg[0]), self.eval(arg[1]))

    def evalOr(self, arg):
        return Or(self.eval(arg[0]), self.eval(arg[1]))

    def evalNot(self, arg):
        return Not(self.eval(arg[0]))

    def evalParens(self, arg):
        return self.eval(arg[0])

    def evalString(self, arg):
        return Text(arg[0])

    def evalEq(self, arg):
        return Eq(arg[0], arg[1])

    def evalNe(self, arg):
        return Ne(arg[0], arg[1])

    def evalGe(self, arg):
        return Ge(arg[0], arg[1])

    def evalLe(self, arg):
        return Le(arg[0], arg[1])

    def evalRange(self, arg):
        return Range(arg[0], arg[1], arg[2])
    
    def evalIn(self, arg):
        return In(arg[0], arg[1])

    def evalSource(self, arg):
        return Source(arg[0])

    def evalQuery(self, arg):
        return Query(None, self.eval(arg[0]))

    def eval(self, arg):
        return self._methods[arg.getName()](arg)

    def __call__(self, query):
        return self.eval(self._parser(query))

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    
